﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  NotificationRules.cs
//    
// Abstract:
//
//  This class represents the root Enhanced Reminder rules
//  
//--

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders
{
    /// <summary>
    /// Class for encapsulation serialized Notification rules.
    /// Implements ObservableCollection to allow XAML binding
    /// </summary>
    [Serializable()]
    public sealed class NotificationRules : ObservableCollection<NotificationRuleItem>
    {
        #region Private members

        /// <summary>
        /// Flag indicating if user should be prompted when deleting rules
        /// </summary>
        private bool _doNotPromptUserOnDelete;

        private TimeZoneInfo _timeZoneInfo;

        #endregion

        /// <summary>
        /// Ctor.
        /// </summary>
        public NotificationRules()
        {
            // Initialize the time zone to the default value.  A future enhancement would be for
            // the rules UI (in the Outlook Tools->Options->EnhancedRemineders tab) to override this value.  
            TimeZone = TimeZoneInfo.Local.Id;
        }

        #region Properties

        /// <summary>
        /// Flag indicating if user should be prompted when deleting rules
        /// </summary>
        public bool DoNotPromptUserOnDelete
        {
            get
            {
                return _doNotPromptUserOnDelete;
            }
            set
            {
                _doNotPromptUserOnDelete = value;
            }
        }

        /// <summary>
        /// Get/set TimeZone info
        /// 
        /// 1. Set takes TimeZone ids of the form "Pacific Standard Time", which are available from the 
        /// registry at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones.
        /// 
        /// 2. Display currently returns the display form of the TimeZone, e.g. 
        /// "(GMT-08:00) Pacific Time (US &amp; Canada)", which can be displayed at the UI level.
        /// </summary>
        public string TimeZone
        {
            get
            {
                if (_timeZoneInfo != null)
                {
                    return _timeZoneInfo.Id;
                }
                else
                {
                    return String.Empty;
                }
            }
            set
            {
                if (value != null)
                {
                    _timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(value);
                }
            }
        }

        /// <summary>
        /// Returns the timezone associated with this set of rules.
        /// </summary>
        public TimeZoneInfo TimeZoneInfo
        {
            get
            {
                return _timeZoneInfo;
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Returns appropriate ReminderItem if the rules apply to this mailbox item
        /// 1. Process active rules in the order in which they appear in the collection (priority sorted list).
        /// 2. Look for rules with Time or People condition first. If any of these are specified, create a ReminderItem.
        /// 3. If none of the rules with Time or People conditions are satisfied, look for all status conditions. Since
        ///    we can't determine status at this point, we'll concatenate all status conditions that are active and 
        ///    let the Dispatcher determines at delivery time whether to send the notification.
        /// </summary>
        /// <param name="notificationData">Instance of INotificationData</param>
        public NotificationRuleItem[] GetMatchingRules(INotificationData notificationData)
        {
            Debug.Assert(notificationData != null, "notificationData != null");
            Debug.Assert(_timeZoneInfo != null, "_timeZoneInfo != null");

            // Convert the request UTC DateTime to the appropriate time zone for these rules
            notificationData = new NotificationData(notificationData.Sender, notificationData.MailboxType,
                TimeZoneInfo.ConvertTimeFromUtc(notificationData.DateTime, _timeZoneInfo));

            List<NotificationRuleItem> matchingRules = new List<NotificationRuleItem>();

            foreach (NotificationRuleItem rule in this)
            {
                if (rule.IsEnabled && rule.MailboxType == notificationData.MailboxType)
                {
                    bool containsStatusCondition;

                    if (rule.IsSatisfied(notificationData, out containsStatusCondition))
                    {
                        matchingRules.Add(rule);

                        if (!containsStatusCondition)
                        {
                            break;
                        }
                    }
                }
            }

            return matchingRules.Count > 0 ? matchingRules.ToArray() : null;
        }
        
        /// <summary>
        /// Gets the first calendar rule that matches the specified data that conatins reminder channels.
        /// </summary>
        /// <param name="notificationData">Data to be matched against the rules</param>
        /// <returns>
        /// The first matching rule or null if no matching rule is found.
        /// </returns>
        public NotificationRuleItem GetFirstMatchingReminderRule(INotificationData notificationData)
        {
            return GetFirstMatchingCalendarRule(notificationData, false);
        }

        /// <summary>
        /// Gets the first calendar rule that matches the specified data that conatins JoinNow channels.
        /// </summary>
        /// <param name="notificationData">Data to be matched against the rules</param>
        /// <returns>
        /// The first matching rule or null if no matching rule is found.
        /// </returns>
        public NotificationRuleItem GetFirstMatchingJoinNowRule(INotificationData notificationData)
        {
            return GetFirstMatchingCalendarRule(notificationData, true);
        }

        /// <summary>
        /// Updates the rule IDs for calendar rules using the previous calendar rules. If a rule hasn't changed, the same rule ID is used; 
        /// otherwsie a new rule ID is assigned to the rule and all subsequent calendar rules.
        /// </summary>
        /// <param name="previousRules">Previous rules</param>
        /// <remarks>
        /// This method ensures that new rule IDs are assigned to calendar rules that differ from the previous calendar rules. This enables 
        /// the server to know which rules-based reminders need to be updated.
        /// </remarks>
        public void UpdateRuleIds(NotificationRules previousRules)
        {
            Debug.Assert(previousRules != null, "previousRules != null");

            int j = 0;

            // If the timezone has changed, all calendar rules need to be assigned a new ID
            if (_timeZoneInfo != null && previousRules._timeZoneInfo != null && _timeZoneInfo.Equals(previousRules._timeZoneInfo))
            {
                int k = 0;

                // Search the current rules for enabled calendar rules
                for (; j < this.Count; j++)
                {
                    if (this[j].IsEnabled && this[j].MailboxType == MailboxType.CalendarItem)
                    {
                        bool rulesMatch = false;

                        // Search for the next enabled calendar rule in the previous rules
                        for (; k < previousRules.Count; k++)
                        {
                            if (previousRules[k].IsEnabled && previousRules[k].MailboxType == MailboxType.CalendarItem)
                            {
                                if (this[j].Equals(previousRules[k]))
                                {
                                    // The rules match so copy the rule ID
                                    this[j].Id = previousRules[k].Id;
                                    rulesMatch = true;
                                    k++;
                                }

                                break;
                            }
                        }

                        if (!rulesMatch)
                        {
                            // Couldn't find a matching calendar rule in the previous rules, so need to generate new IDs for the 
                            // rest of the current calendar item rules
                            break;
                        }
                    }
                }
            }

            // Assign a new ID to the remaining calendar rules
            for (; j < this.Count; j++)
            {
                if (this[j].IsEnabled && this[j].MailboxType == MailboxType.CalendarItem)
                {
                    // Assign new rule ID
                    this[j].Id = Guid.NewGuid();
                }
            }
        }

        /// <summary>
        /// Gets the first matching calendar rule that matches the specified data.
        /// </summary>
        /// <param name="notificationData">Data to be matched against the rules</param>
        /// <param name="joinNow">Indicates if joinNow rules are being requested</param>
        /// <returns>
        /// The first matching rule or null if no matching rule is found.
        /// </returns>
        private NotificationRuleItem GetFirstMatchingCalendarRule(INotificationData notificationData, bool joinNow)
        {
            Debug.Assert(notificationData != null, "notificationData != null");
            Debug.Assert(_timeZoneInfo != null, "_timeZoneInfo != null");

            NotificationRuleItem matchingRule = null;

            // Convert the request UTC DateTime to the appropriate time zone for these rules
            notificationData = new NotificationData(notificationData.Sender, notificationData.MailboxType, 
                TimeZoneInfo.ConvertTimeFromUtc(notificationData.DateTime, _timeZoneInfo));

            foreach (NotificationRuleItem rule in this)
            {
                if (rule.IsEnabled && rule.MailboxType == notificationData.MailboxType &&
                    (joinNow ? rule.IsJoinNowEnabled : rule.IsNotificationEnabled))
                {
                    bool containsStatusCondition;

                    if (rule.IsSatisfied(notificationData, out containsStatusCondition))
                    {
                        Debug.Assert(!containsStatusCondition, "!containsStatusCondition");
                        matchingRule = rule;
                        break;
                    }
                }
            }

            return matchingRule;
        }

        /// <summary>
        /// Log the each rule to the verbose trace logs.
        /// </summary>
        /// <param name="context">The logging context.</param>
        internal void LogTraceInfo(Context context)
        {
            Debug.Assert(context != null, "context");

            foreach (NotificationRuleItem rule in this)
            {
                rule.LogTraceInfo(context);
            }
        }

        #endregion Methods
    }
}
